﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;


namespace BloodBot
{
    class PlayingState : BaseState
    {

        //Level
        Matrix worldTranslation = Matrix.Identity;
        Matrix worldRotation = Matrix.Identity;
        Matrix worldScale = Matrix.Identity;
        Texture2D bloodTex;
        Texture2D[] domeTex;
        VertexPositionNormalTexture[] verts;
        VertexBuffer vertexBuffer;
        BasicEffect effect;
        BasicEffect bulletEffect;
        Stopwatch levelTimer;
        float groundSize;
        Model skyDome;
        float skyDomeRotation = 1;
        BloodCell bloodCell;

        //new Level stuff
        Map activeMap;

        //HUD
        HUD hud;
        Camera camera;
        SpriteFont font;
        SpriteBatch spriteBatch;        

        //Inputs
        Input input;

        //Enemies
        EnemyManager enemyManager;
        BasicEffect enemyEffect;
        Stopwatch enemyTimer;
        Color enemyColour;
        Vector3 enemyPosition;
        // USED to give buffer to spawn location of enemies so they don't spawn outside the arena.
        private const int SpawnPerimeterBuffer = 5;

        float enemySize;
        float[] enemySizeArray;
        int enemyCount;
        int enemyIndexNo = 0;
        // Level stuff
        private const int ENEMIES_PER_LEVEL = 5;
        private int enemiesRemaining;
        private int level = 1;
        private Boolean isTransitioningLevel = false;
        private Stopwatch levelTransitionTimer;
        private bool displayNextLevelText;
        bool debugOn = false;

        //Shooting
        public List<Shooting> shooting;
        public Shooting shot;
        Color shotColour;
        Vector3 shotPosition;
        float shotSize = 0.009f;
        Stopwatch shootingTimer;
        bool unlockShooting = true;
        Texture2D whitePixelTex;
        int score;
        int shotsFired;

        BoundingSphereRenderer bsRender;
        bool RenderBoundingBoxes = false;

        public Sounds sounds;

        //public Collisions collisions;

        public Player player;

        public Random random = new Random();

        int alphaCounter = 1;
        Boolean increasing = true;

        Pathfinding pathFinding;
        List<Cell> cells;
        Cell startPoint = null;
        

        public PlayingState(StateManager stateManager, int level = 1, int enemyCount = 3) : base(stateManager)
        {
            this.level = level;
            this.enemyCount = enemyCount;

            spriteBatch = stateManager.spriteBatch;
            font = stateManager.Game.Content.Load<SpriteFont>("myFont");
            
        }

        public new void Initialize()
        {
            stateManager.Game.IsMouseVisible = false;

            input = new Input(stateManager.Game.GraphicsDevice.Viewport.Width,
                stateManager.Game.GraphicsDevice.Viewport.Height);

            camera = new Camera(stateManager.Game, input, new Vector3(0, 0, 4), 
                Vector3.Zero, Vector3.Up);
            stateManager.Game.Components.Add(camera);
            groundSize = 15;
            bloodCell = new BloodCell(stateManager.Game, new Vector3(0, -1, 0), groundSize, 10, bloodTex);

            enemyTimer= new Stopwatch();
            levelTimer = new Stopwatch();
            levelTimer.Start();
            score = 0;

            enemySizeArray = new float[4];

            enemySizeArray[0] = 0.2f;
            enemySizeArray[1] = 0.3f;
            enemySizeArray[2] = 0.4f;
            enemySizeArray[3] = 0.5f;

            player = new Player(stateManager.Game, camera);
            player.Initialize();
            shooting = new List<Shooting>();

            shootingTimer = new Stopwatch();

            sounds = new Sounds(stateManager.Game);
            bsRender = new BoundingSphereRenderer();
            

            enemiesRemaining = ENEMIES_PER_LEVEL * (level);
            
            levelTransitionTimer = new Stopwatch();

            activeMap = new Map(stateManager.Game, 30, 1.0f , -1.0f, 01, "activeMap");
            while (level > 3)
            { level -= 3; }
            activeMap.SetSomeWallsUp(level);
            activeMap.TrimCellsOutsideBoundary(groundSize);

            this.enemyManager = new EnemyManager(stateManager.Game, activeMap);
            enemyManager.Initialize();

            base.Initialize();
        }

        protected override void LoadContent()
        {
            
            eff =  new BasicEffect(stateManager.Game.GraphicsDevice);
            skyDome = LoadModel("dome", out domeTex);
            input.CenterMouse();
            
            hud = new HUD(stateManager.Game, enemyManager.GetEnemies(), player, levelTimer,  spriteBatch, font);
            hud.Initialize();
            hud.SetEnemiesRemaining(enemiesRemaining);

            bloodTex = stateManager.Game.Content.Load<Texture2D>(@"Models\bloodtex");
            whitePixelTex = stateManager.Game.Content.Load<Texture2D>(@"WhitePixel");
            Vector3 groundNormal = new Vector3(0, 1, 0);

            verts = bloodCell.buildGround();

            //Set vertex data in vertexBuffer
            vertexBuffer = new VertexBuffer(stateManager.Game.GraphicsDevice, typeof(VertexPositionNormalTexture), verts.Length, BufferUsage.None);
            vertexBuffer.SetData(verts);

            //init the BasicEffect
            effect = new BasicEffect(stateManager.Game.GraphicsDevice);
            bulletEffect = new BasicEffect(stateManager.Game.GraphicsDevice);
            enemyEffect = new BasicEffect(stateManager.Game.GraphicsDevice);

            sounds.Load();

            foreach (Enemy enemy in enemyManager.GetEnemies())
            {
                enemy.Load(enemyManager.GetCollisions());
            }

            foreach (Shooting shot in shooting)
            {
                shot.Load();

            }
         
        }

        public override void Update(GameTime gameTime)
        {
            if (!isStateInitialized)
            {
                this.Initialize();
            }
            
            input.Update();

            if (stateManager.Game.IsActive)
            {

                //Toggle Fire Mode
                if (input.MouseRClickSwitch())
                {
                    if (unlockShooting)
                    {
                        //unlockShooting = false;
                    }
                    else
                    {
                        unlockShooting = true;
                    }
                }
                input.CenterMouse();
            }
            if (!enemyTimer.IsRunning)
            {
                enemyTimer.Start();
            }

            if (!shootingTimer.IsRunning)
            {
                shootingTimer.Start();
            }
            
            ShootingUpdates(gameTime);
            
            //Centering the mouse here is a quick fix, there must be a more elgant solution
            //The problem resides in the mouse.SetPos changing the oldmState.
            //while the oldmState is used for clicks as well as the mouse x and y.
            //move this after the camera.update or before the shooting update and see what i mean. - Jesse
            
            camera.Update(gameTime, enemyManager.GetCollisions(), groundSize, activeMap);
            camera.checkWallCollisions(activeMap.getWalls(), gameTime);
            sounds.Update(gameTime, player);
            player.Update(gameTime);
            hud.Update(gameTime);
            EnemyUpdates(gameTime);


            if (input.Debug() && !debugOn)
            {   
                debugOn = true;
                camera.toggleFreeLook();
                camera.reset(new Vector3(0, 10, 0), Vector3.Zero, Vector3.Forward);
            }
            else if (input.Debug() && debugOn)
            {
                debugOn = false;
                camera.toggleFreeLook();
                camera.reset(new Vector3(0, 0, 5), Vector3.Zero, Vector3.Up);
            }

            if (input.BSwitch() && RenderBoundingBoxes)
                RenderBoundingBoxes = false;
            else if (input.BSwitch() && !RenderBoundingBoxes)
                RenderBoundingBoxes = true;
            


            if (player.Dead())
            {
                camera.Dispose();
                player.Dispose();
                sounds.heartBeatInstance.Dispose();

                foreach (Enemy enemies in enemyManager.GetEnemies())
                {
                    enemies.Dispose();
                }

                stateManager.gameStats.UpdateStats(score, shotsFired, 0, levelTimer.Elapsed);

                sounds.PlayGameOverSound();
                stateManager.GoToGameOverState(score);
            }
            if (enemiesRemaining <= 0 && enemyManager.GetEnemies().Count <= 0)
            {
                // next level
                if (isTransitioningLevel)
                {
                    if (levelTransitionTimer.Elapsed.Seconds >= 5)
                    {
                        NewLevel();
                    }
                }
                else
                {
                    isTransitioningLevel = true;
                    displayNextLevelText = true;
                    levelTransitionTimer.Restart();
                    sounds.PlayNextLevelSound();
                    sounds.heartBeatInstance.Dispose();
                    level++;
                    stateManager.gameStats.UpdateStats(score, shotsFired, 1, levelTimer.Elapsed);               
                }
            }

            /*
            Cell destination = activeMap.VectorToCell(player.pos);
            if (destination != null && destination.getType().Equals("Ground"))
            {
                LinkedList<Cell> path = pathFinding.NavigateTo(startPoint, destination);
                if (path != null)
                {
                    foreach (Cell cell in path)
                    {
                        cell.setIsPath(true);
                    }
                }

            }
             * */
        }

        private void NewLevel()
        {
            stateManager.GoToLungLabState(level, enemyCount);
        }

        private void EnemyUpdates(GameTime gameTime)
        {
            foreach (Enemy enemy in enemyManager.GetEnemies())
            {
                enemy.Update(gameTime, player, random, groundSize, enemyManager.GetCollisions(), activeMap, sounds);
                foreach (Enemy secondEnemy in enemyManager.GetEnemies())
                {
                    if (enemyManager.GetCollisions().CheckSphereCollison(enemy.boundingSphere, secondEnemy.boundingSphere) && enemy.indexNo != secondEnemy.indexNo)
                    {
                        enemy.MovePoints(random, groundSize, true);
                        secondEnemy.MovePoints(random, groundSize, true);
                    }
                }

            }

            if (!isTransitioningLevel)
            {
                if (enemyTimer.Elapsed.Seconds >= 2 && enemyManager.GetEnemies().Count < enemiesRemaining)
                {
                    if (enemyManager.GetEnemies().Count < enemyCount)
                    {
                        SpawnEnemy();
                    }

                    enemyTimer.Restart();
                }
            }

            DestroyEnemy();
        }

        private void ShootingUpdates(GameTime gameTime)
        {
            if (stateManager.Game.IsActive)
            {
                if (input.MouseLClickSwitch() && !unlockShooting)
                {
                    if (shootingTimer.Elapsed.Milliseconds > 100)
                    {
                        shootingTimer.Restart();

                        SpawnShot();
                        sounds.PlayLaserSound();
                    }
                }
                else if (input.MouseLClick() && unlockShooting)
                {
                    if (shootingTimer.Elapsed.Milliseconds > 100)
                    {
                        shootingTimer.Restart();

                        SpawnShot();
                        sounds.PlayLaserSound();
                    }
                }
            }
            
            foreach (Shooting shot in shooting)
            {
                shot.Update(gameTime, enemyManager.GetCollisions());

                foreach (Enemy enemy in enemyManager.GetEnemies())
                {
                    if (enemyManager.GetCollisions().CheckSphereCollison(shot.boundingSphere, enemy.boundingSphere))
                    {
                        shot.removeMe = true;
                        enemy.TakeDamage(shot.GetDamage());
                        sounds.PlayLaserCollisionSound(enemy.position, player.pos);
                    }

                    if (shot.removeMe == false)
                    {
                        foreach (Cell cell in activeMap.Cells)
                        {
                            if (cell.type == "Wall")
                            {
                                if (enemyManager.GetCollisions().checkBulletCollision(shot.boundingSphere, cell.BoundBox))
                                {
                                    shot.removeMe = true;
                                    sounds.PlayLaserCollisionSound(enemy.position, player.pos);
                                }
                            }
                        }
                    }


                }


            }

            DestroyShot();
        }

        public override void Draw(GameTime gameTime)
        {
            if (!isStateInitialized)
            {
                stateManager.Game.GraphicsDevice.Clear(Color.Black);
                // it's still loading! display some text
                spriteBatch.Begin();
                String loading = "Loading!";
                DrawText(loading, new Vector2((stateManager.Game.GraphicsDevice.Viewport.Width - font.MeasureString(loading).X) / 2, (stateManager.Game.GraphicsDevice.Viewport.Height  - font.MeasureString(loading).Y) / 2), Color.White);
                spriteBatch.End();
                stateManager.Game.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            } 
            else 
            {
                stateManager.Game.GraphicsDevice.Clear(Color.Black);
                DrawSkyDome();
                stateManager.Game.GraphicsDevice.SetVertexBuffer(vertexBuffer);

                
                stateManager.Game.GraphicsDevice.DepthStencilState = DepthStencilState.Default;

                effect.World = worldRotation * worldTranslation * worldScale;
                effect.View = camera.view;
                effect.Projection = camera.projection;
                effect.Texture = bloodTex;
                effect.TextureEnabled = true;
                effect.EnableDefaultLighting();

                foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                {
                    pass.Apply();

                    stateManager.Game.GraphicsDevice.DrawUserPrimitives<VertexPositionNormalTexture>
                        (PrimitiveType.TriangleList, verts, 0, bloodCell.getVertsToDraw());

                }
                //DRAW BOUNDING BOXES FIRST
                if (RenderBoundingBoxes)
                {
                    foreach (BoundingBox bb in activeMap.GetBoundingBoxList())
                    {
                        BoundingBoxRenderer.Render(bb, stateManager.Game.GraphicsDevice, effect.View, effect.Projection, whitePixelTex);
                    }
                    foreach (Enemy e in enemyManager.GetEnemies())
                    {
                        bsRender.Render(e.boundingSphere, stateManager.Game.GraphicsDevice, camera.view, camera.projection, Color.Blue);
                        e.DrawStateText(camera);
                    }
                    bsRender.Render(camera.boundingSphere, stateManager.Game.GraphicsDevice, camera.view, camera.projection, Color.White);
                }
                
                activeMap.DrawAllCells(camera, debugOn);

                foreach (Enemy enemy in enemyManager.GetEnemies())
                {
                    // Every time spritebatch is used, you need to reset the depthStencilState.
                    stateManager.Game.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
                    enemy.Draw(camera);
                }

                foreach (Shooting shot in shooting)
                {
                    shot.Draw(camera);
                }

                if (debugOn)
                {
                    spriteBatch.Begin();
                    //
                    spriteBatch.End();
                    stateManager.Game.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
                }

                if (displayNextLevelText)
                {
                    int finishedLevel = level - 1;
                    //String loadingMessage = "Level " + finishedLevel + " Complete! Level " + level + " is about to begin!";
                    String loadingMessage = "Level " + finishedLevel + " Complete! " + "Entering Lung Lab!";

                    Vector2 textLocation = new Vector2((stateManager.Game.GraphicsDevice.Viewport.Width - font.MeasureString(loadingMessage).X) / 2,
                                                        ((stateManager.Game.GraphicsDevice.Viewport.Height - font.MeasureString(loadingMessage).Y) - 100) / 2);
                    // draw text saying level complete
                    spriteBatch.Begin();
                    
                    DrawText(loadingMessage, textLocation, Color.White);
                    spriteBatch.End();
                    stateManager.Game.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
                }

                
                hud.Draw();
                stateManager.Game.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
                

            }
        }

        private void DrawText(String text, Vector2 textVec, Color c)
        {
            spriteBatch.DrawString(font, text, textVec, c);
        }

        private void DrawSkyDome()
        {
            SamplerState ss = new SamplerState();
            ss.AddressU = TextureAddressMode.Clamp;
            ss.AddressV = TextureAddressMode.Clamp;
            stateManager.Game.GraphicsDevice.SamplerStates[0] = ss;

            DepthStencilState dss = new DepthStencilState();
            dss.DepthBufferEnable = false;
            stateManager.Game.GraphicsDevice.DepthStencilState = dss;

            Matrix[] modelTransforms = new Matrix[skyDome.Bones.Count];
            skyDome.CopyAbsoluteBoneTransformsTo(modelTransforms);

            Matrix domeMatrix = Matrix.CreateScale(20) * Matrix.CreateRotationX((float)Math.PI * 0.001f * skyDomeRotation++) * Matrix.CreateTranslation(camera.cameraPosition);

            foreach (ModelMesh mesh in skyDome.Meshes)
            {
                foreach (BasicEffect currentEffect in mesh.Effects)
                {
                    Matrix worldMatrix = modelTransforms[mesh.ParentBone.Index] * domeMatrix;
                    currentEffect.World = worldMatrix;
                    currentEffect.View = camera.view;
                    currentEffect.Projection = camera.projection;
                    currentEffect.TextureEnabled = true;
                    currentEffect.Texture = domeTex[0];
                    currentEffect.Alpha = GenerateSkyAlpha();
                }
                mesh.Draw();
            }

            domeMatrix = Matrix.CreateScale(20) * Matrix.CreateRotationX((float)Math.PI) * Matrix.CreateRotationX((float)Math.PI * 0.001f * skyDomeRotation++) * Matrix.CreateTranslation(camera.cameraPosition);

            foreach (ModelMesh mesh in skyDome.Meshes)
            {
                foreach (BasicEffect currentEffect in mesh.Effects)
                {
                    Matrix worldMatrix = modelTransforms[mesh.ParentBone.Index] * domeMatrix;
                    currentEffect.World = worldMatrix;
                    currentEffect.View = camera.view;
                    currentEffect.Projection = camera.projection;
                }
                mesh.Draw();
            }

            dss = new DepthStencilState();
            dss.DepthBufferEnable = true;
            stateManager.Game.GraphicsDevice.DepthStencilState = dss;

        }

        private float GenerateSkyAlpha()
        {
            if (alphaCounter < 100 && alphaCounter > 20)
            {
                if (increasing)
                {
                    alphaCounter++;
                }
                else
                {
                    alphaCounter--;
                }
            }
            else if (alphaCounter <= 20)
            {
                increasing = true;
                alphaCounter++;
            }
            else if (alphaCounter >= 100)
            {
                increasing = false;
                alphaCounter--;
            }
            float result = (float)alphaCounter / 100;
            return result;
        }

        
        public void SpawnEnemy()
        {
            enemyColour = Color.Red;
                       
            enemySize = enemySizeArray[random.Next(0, 4)];

            checkPosition();

            enemyManager.SpawnNewEnemy(enemyPosition, enemySize, enemyIndexNo, enemyEffect, spriteBatch);

            enemyIndexNo++;
        }

        public void DestroyEnemy()
        {
            for (int i = enemyManager.GetEnemies().Count - 1; i >= 0; i--)
            {
                if (enemyManager.GetEnemies()[i].health <= 0)
                {
                    enemyManager.GetEnemies().RemoveAt(i);
                    sounds.PlayEnemyDeathSound(random.Next(3));
                    enemiesRemaining--;
                    hud.SetEnemiesRemaining(enemiesRemaining);
                    adjustScore(1);
                }
            }
        }

        public void SpawnShot()
        {
            shotsFired++;
            shotColour = Color.Yellow;
            shotPosition = camera.cameraPosition;

            Vector3 direction = camera.cameraDirection;
            
            shot = new Shooting(stateManager.Game, shotColour, shotPosition, shotSize, direction, groundSize + 2, bulletEffect);
            shooting.Add(shot);                   


            shot.Initialize();
            shot.Load();
        }

        public Vector3 getShotDirection()
        {
            Viewport vp = stateManager.Game.GraphicsDevice.Viewport;

            MouseState ms = new MouseState();

            ms = Mouse.GetState();
        
            Vector3 direction = camera.cameraDirection;
            direction.Normalize();

            return direction;
        }

        public void DestroyShot()
        {
            for (int i = shooting.Count - 1; i >= 0; i--)
            {
                if (shooting[i].removeMe)
                {
                    shooting.RemoveAt(i);
                }
            }
        }

        private void checkPosition()
        {
            bool restartLoop = false;
            float distance;

            do
            {
                enemyPosition = new Vector3(random.Next((int)(-groundSize + SpawnPerimeterBuffer), (int)(groundSize - SpawnPerimeterBuffer)), 
                    -0.75f + enemySize, 
                    random.Next((int)(-groundSize + SpawnPerimeterBuffer), (int)(groundSize - SpawnPerimeterBuffer)));

                foreach (Enemy enemy in enemyManager.GetEnemies())
                {
                    if (enemyPosition == enemy.enemyTranslation.Translation)
                    {
                        restartLoop = true;
                        break;
                    }
                    else
                    {
                        restartLoop = false;
                    }
                }

                distance = Vector3.Distance(enemyPosition, player.pos);

                if (distance <= 2)
                {
                    restartLoop = true;
                }

            } while (restartLoop);

        }
        private void adjustScore(int adjustment)
        {
            score += adjustment;
        }
    }
}
